/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
function mxEdgeSegmentHandler(state)
{
    mxEdgeHandler.call(this, state);
};

/**
 * Extends mxEdgeHandler.
 */
mxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler);

/**
 * Function: getCurrentPoints
 *
 * Returns the current absolute points.
 */
mxEdgeSegmentHandler.prototype.getCurrentPoints = function()
{
    var pts = this.state.absolutePoints;

    if (pts != null)
    {
        // Special case for straight edges where we add a virtual middle handle for moving the edge
        var tol = Math.max(1, this.graph.view.scale);

        if (pts.length == 2 || (pts.length == 3 &&
            (Math.abs(pts[0].x - pts[1].x) < tol && Math.abs(pts[1].x - pts[2].x) < tol ||
                Math.abs(pts[0].y - pts[1].y) < tol && Math.abs(pts[1].y - pts[2].y) < tol)))
        {
            var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
            var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;

            pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]];
        }
    }

    return pts;
};

/**
 * Function: getPreviewPoints
 *
 * Updates the given preview state taking into account the state of the constraint handler.
 */
mxEdgeSegmentHandler.prototype.getPreviewPoints = function(point)
{
    if (this.isSource || this.isTarget)
    {
        return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);
    }
    else
    {
        var pts = this.getCurrentPoints();
        var last = this.convertPoint(pts[0].clone(), false);
        point = this.convertPoint(point.clone(), false);
        var result = [];

        for (var i = 1; i < pts.length; i++)
        {
            var pt = this.convertPoint(pts[i].clone(), false);

            if (i == this.index)
            {
                if (Math.round(last.x - pt.x) == 0)
                {
                    last.x = point.x;
                    pt.x = point.x;
                }

                if (Math.round(last.y - pt.y) == 0)
                {
                    last.y = point.y;
                    pt.y = point.y;
                }
            }

            if (i < pts.length - 1)
            {
                result.push(pt);
            }

            last = pt;
        }

        // Replaces single point that intersects with source or target
        if (result.length == 1)
        {
            var source = this.state.getVisibleTerminalState(true);
            var target = this.state.getVisibleTerminalState(false);
            var scale = this.state.view.getScale();
            var tr = this.state.view.getTranslate();

            var x = result[0].x * scale + tr.x;
            var y = result[0].y * scale + tr.y;

            if ((source != null && mxUtils.contains(source, x, y)) ||
                (target != null && mxUtils.contains(target, x, y)))
            {
                result = [point, point];
            }
        }

        return result;
    }
};

/**
 * Function: updatePreviewState
 *
 * Overridden to perform optimization of the edge style result.
 */
mxEdgeSegmentHandler.prototype.updatePreviewState = function(edge, point, terminalState, me)
{
    mxEdgeHandler.prototype.updatePreviewState.apply(this, arguments);

    // Checks and corrects preview by running edge style again
    if (!this.isSource && !this.isTarget)
    {
        point = this.convertPoint(point.clone(), false);
        var pts = edge.absolutePoints;
        var pt0 = pts[0];
        var pt1 = pts[1];

        var result = [];

        for (var i = 2; i < pts.length; i++)
        {
            var pt2 = pts[i];

            // Merges adjacent segments only if more than 2 to allow for straight edges
            if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
                (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))
            {
                result.push(this.convertPoint(pt1.clone(), false));
            }

            pt0 = pt1;
            pt1 = pt2;
        }

        var source = this.state.getVisibleTerminalState(true);
        var target = this.state.getVisibleTerminalState(false);
        var rpts = this.state.absolutePoints;

        // A straight line is represented by 3 handles
        if (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 ||
            Math.round(pts[0].y - pts[pts.length - 1].y) == 0))
        {
            result = [point, point];
        }
        // Handles special case of transitions from straight vertical to routed
        else if (pts.length == 5 && result.length == 2 && source != null && target != null &&
            rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0)
        {
            var view = this.graph.getView();
            var scale = view.getScale();
            var tr = view.getTranslate();

            var y0 = view.getRoutingCenterY(source) / scale - tr.y;

            // Use fixed connection point y-coordinate if one exists
            var sc = this.graph.getConnectionConstraint(edge, source, true);

            if (sc != null)
            {
                var pt = this.graph.getConnectionPoint(source, sc);

                if (pt != null)
                {
                    this.convertPoint(pt, false);
                    y0 = pt.y;
                }
            }

            var ye = view.getRoutingCenterY(target) / scale - tr.y;

            // Use fixed connection point y-coordinate if one exists
            var tc = this.graph.getConnectionConstraint(edge, target, false);

            if (tc)
            {
                var pt = this.graph.getConnectionPoint(target, tc);

                if (pt != null)
                {
                    this.convertPoint(pt, false);
                    ye = pt.y;
                }
            }

            result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)];
        }

        this.points = result;

        // LATER: Check if points and result are different
        edge.view.updateFixedTerminalPoints(edge, source, target);
        edge.view.updatePoints(edge, this.points, source, target);
        edge.view.updateFloatingTerminalPoints(edge, source, target);
    }
};

/**
 * Overriden to merge edge segments.
 */
mxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
{
    var model = this.graph.getModel();
    var geo = model.getGeometry(edge);
    var result = null;

    // Merges adjacent edge segments
    if (geo != null && geo.points != null && geo.points.length > 0)
    {
        var pts = this.abspoints;
        var pt0 = pts[0];
        var pt1 = pts[1];
        result = [];

        for (var i = 2; i < pts.length; i++)
        {
            var pt2 = pts[i];

            // Merges adjacent segments only if more than 2 to allow for straight edges
            if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
                (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))
            {
                result.push(this.convertPoint(pt1.clone(), false));
            }

            pt0 = pt1;
            pt1 = pt2;
        }
    }

    model.beginUpdate();
    try
    {
        if (result != null)
        {
            var geo = model.getGeometry(edge);

            if (geo != null)
            {
                geo = geo.clone();
                geo.points = result;

                model.setGeometry(edge, geo);
            }
        }

        edge = mxEdgeHandler.prototype.connect.apply(this, arguments);
    }
    finally
    {
        model.endUpdate();
    }

    return edge;
};

/**
 * Function: getTooltipForNode
 *
 * Returns no tooltips.
 */
mxEdgeSegmentHandler.prototype.getTooltipForNode = function(node)
{
    return null;
};

/**
 * Function: createBends
 *
 * Adds custom bends for the center of each segment.
 */
mxEdgeSegmentHandler.prototype.start = function(x, y, index)
{
    mxEdgeHandler.prototype.start.apply(this, arguments);

    if (this.bends != null && this.bends[index] != null &&
        !this.isSource && !this.isTarget)
    {
        mxUtils.setOpacity(this.bends[index].node, 100);
    }
};

/**
 * Function: createBends
 *
 * Adds custom bends for the center of each segment.
 */
mxEdgeSegmentHandler.prototype.createBends = function()
{
    var bends = [];

    // Source
    var bend = this.createHandleShape(0);
    this.initBend(bend);
    bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
    bends.push(bend);

    var pts = this.getCurrentPoints();

    // Waypoints (segment handles)
    if (this.graph.isCellBendable(this.state.cell))
    {
        if (this.points == null)
        {
            this.points = [];
        }

        for (var i = 0; i < pts.length - 1; i++)
        {
            bend = this.createVirtualBend();
            bends.push(bend);
            var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0;

            // Special case where dy is 0 as well
            if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2)
            {
                horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0;
            }

            bend.setCursor((horizontal) ? 'col-resize' : 'row-resize');
            this.points.push(new mxPoint(0,0));
        }
    }

    // Target
    var bend = this.createHandleShape(pts.length);
    this.initBend(bend);
    bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
    bends.push(bend);

    return bends;
};

/**
 * Function: redraw
 *
 * Overridden to invoke <refresh> before the redraw.
 */
mxEdgeSegmentHandler.prototype.redraw = function()
{
    this.refresh();
    mxEdgeHandler.prototype.redraw.apply(this, arguments);
};

/**
 * Function: redrawInnerBends
 *
 * Updates the position of the custom bends.
 */
mxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe)
{
    if (this.graph.isCellBendable(this.state.cell))
    {
        var pts = this.getCurrentPoints();

        if (pts != null && pts.length > 1)
        {
            var straight = false;

            // Puts handle in the center of straight edges
            if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0)
            {
                straight = true;

                if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0)
                {
                    var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
                    pts[1] = new mxPoint(cx, pts[1].y);
                    pts[2] = new mxPoint(cx, pts[2].y);
                }
                else
                {
                    var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
                    pts[1] = new mxPoint(pts[1].x, cy);
                    pts[2] = new mxPoint(pts[2].x, cy);
                }
            }

            for (var i = 0; i < pts.length - 1; i++)
            {
                if (this.bends[i + 1] != null)
                {
                    var p0 = pts[i];
                    var pe = pts[i + 1];
                    var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
                    var b = this.bends[i + 1].bounds;
                    this.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2),
                        Math.floor(pt.y - b.height / 2), b.width, b.height);
                    this.bends[i + 1].redraw();

                    if (this.manageLabelHandle)
                    {
                        this.checkLabelHandle(this.bends[i + 1].bounds);
                    }
                }
            }

            if (straight)
            {
                mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity);
                mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity);
            }
        }
    }
};
